package com.aiwei.tdjk.common.reposiotry.base;

import com.aiwei.tdjk.common.entity.BaseEntity;
import com.aiwei.tdjk.common.entity.search.SearchOperator;
import com.aiwei.tdjk.common.entity.search.Searchable;
import com.aiwei.tdjk.common.entity.search.filter.SearchFilter;
import com.aiwei.tdjk.common.entity.search.filter.SearchFilterHelper;
import com.aiwei.tdjk.common.plugin.entity.Treeable;
import com.aiwei.tdjk.common.reposiotry.callback.SearchCallback;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.Set;

/**
 * Created by konghao on 2016/12/7.
 */
public class BaseRepositoryImpl<M extends BaseEntity<ID> & Treeable<ID>, ID extends Serializable> extends SimpleJpaRepository<M,ID>
        implements BaseRepository<M,ID> {

    private final EntityManager entityManager;

	public static final String LOGIC_DELETE_ALL_QUERY_STRING = "update %s x set x.deleted=true where x in (?1)";
	public static final String DELETE_ALL_QUERY_STRING = "delete from %s x where x in (?1)";
	public static final String FIND_QUERY_STRING = "from %s x where 1=1 ";
	public static final String COUNT_QUERY_STRING = "select count(x) from %s x where 1=1 ";

	private final JpaEntityInformation<M, ID> entityInformation;

	private final RepositoryHelper repositoryHelper;


	private Class<M> entityClass;
	private String entityName;
	private String idName;


	/**
	 * 查询所有的QL
	 */
	private String findAllQL;
	/**
	 * 统计QL
	 */
	private String countAllQL;

	private SearchCallback searchCallback = SearchCallback.DEFAULT;



    //父类没有不带参数的构造方法，这里手动构造父类
    public BaseRepositoryImpl(JpaEntityInformation<M, ID> entityInformation, EntityManager entityManager) {
//        super(domainClass, entityManager);
//        this.entityManager = entityManager;

		super(entityInformation, entityManager);

		this.entityInformation = entityInformation;
		this.entityClass = this.entityInformation.getJavaType();
		this.entityName = this.entityInformation.getEntityName();
		this.idName = this.entityInformation.getIdAttributeNames().iterator().next();
		this.entityManager = entityManager;

		repositoryHelper = new RepositoryHelper(entityClass,entityManager);

		findAllQL = String.format(FIND_QUERY_STRING, entityName);
		countAllQL = String.format(COUNT_QUERY_STRING, entityName);
	}

    //通过EntityManager来完成查询
    @SuppressWarnings("unchecked")
	@Override
    public List<Object[]> listBySQL(String sql) {
        return entityManager.createNativeQuery(sql).getResultList();
    }

	@Override
	public void updateBySql(String sql,Object...args) {
    	Query query = entityManager.createNativeQuery(sql);
    	int i = 0;
    	for(Object arg:args) {
    		query.setParameter(++i,arg);
    	}
    	query.executeUpdate();
	}

	@Override
	public void updateByHql(String hql,Object...args) {
    	Query query = entityManager.createQuery(hql);
    	int i = 0;
    	for(Object arg:args) {
    		System.out.println(arg);
    		query.setParameter(++i,arg);
    	}
    	query.executeUpdate();
	}

	/**
	 * 根据条件查询所有
	 * 条件 + 分页 + 排序
	 *
	 * @param searchable
	 * @return
	 */
	@Override
	public Page<M> findAll(Searchable searchable) {
		List<M> list = repositoryHelper.findAll(findAllQL, searchable, searchCallback);
		long total = searchable.hasPageable() ? count(searchable) : list.size();
		return new PageImpl<M>(
				list,
				searchable.getPage(),
				total
		);
	}

	@Override
	public long count(final Searchable searchable) {
		return repositoryHelper.count(countAllQL, searchable, searchCallback);
	}


	/**
	 *   树相关的操作
	 */
	private String DELETE_CHILDREN_QL;
	private String UPDATE_CHILDREN_PARENT_IDS_QL;
	private  String FIND_SELF_AND_NEXT_SIBLINGS_QL;

	private  String FIND_NEXT_WEIGHT_QL;


	@Transactional
	public void deleteSelfAndChild(M m) {
		DELETE_CHILDREN_QL = String.format("delete from %s where id=?1 or parentIds like concat(?2, %s)", entityName, "'%'");

		repositoryHelper.batchUpdate(DELETE_CHILDREN_QL, m.getId(), m.makeSelfAsNewParentIds());
	}

	public int nextWeight(ID id) {
		FIND_NEXT_WEIGHT_QL =
				String.format("select case when max(weight) is null then 1 else (max(weight) + 1) end from %s where parentId = ?1", entityName);
		return repositoryHelper.<Integer>findOne(FIND_NEXT_WEIGHT_QL, id);
	}

//	public M save(M m) {
//		if (m.getWeight() == null) {
//			m.setWeight(nextWeight(m.getParentId()));
//		}
//		return super.save(m);
//	}



	public void deleteSelfAndChild(List<M> mList) {
		for (M m : mList) {
			deleteSelfAndChild(m);
		}
	}

	public void saveappendChild(M parent, M child) {
		child.setParentId(parent.getId());
		child.setParentIds(parent.makeSelfAsNewParentIds());
		child.setWeight(nextWeight(parent.getId()));
		super.save(child);
	}




	/**
	 * 移动节点
	 * 根节点不能移动
	 *
	 * @param source   源节点
	 * @param target   目标节点
	 * @param moveType 位置
	 */
	public void move(M source, M target, String moveType) {
		if (source == null || target == null || source.isRoot()) { //根节点不能移动
			return;
		}

		//如果是相邻的兄弟 直接交换weight即可
		boolean isSibling = source.getParentId().equals(target.getParentId());
		boolean isNextOrPrevMoveType = "next".equals(moveType) || "prev".equals(moveType);
		if (isSibling && isNextOrPrevMoveType && Math.abs(source.getWeight() - target.getWeight()) == 1) {

			//无需移动
			if ("next".equals(moveType) && source.getWeight() > target.getWeight()) {
				return;
			}
			if ("prev".equals(moveType) && source.getWeight() < target.getWeight()) {
				return;
			}
			int sourceWeight = source.getWeight();
			source.setWeight(target.getWeight());
			target.setWeight(sourceWeight);
			return;
		}

		//移动到目标节点之后
		if ("next".equals(moveType)) {
			List<M> siblings = findSelfAndNextSiblings(target.getParentIds(), target.getWeight());
			siblings.remove(0);//把自己移除

			if (siblings.size() == 0) { //如果没有兄弟了 则直接把源的设置为目标即可
				int nextWeight = nextWeight(target.getParentId());
				updateSelftAndChild(source, target.getParentId(), target.getParentIds(), nextWeight);
				return;
			} else {
				moveType = "prev";
				target = siblings.get(0); //否则，相当于插入到实际目标节点下一个节点之前
			}
		}

		//移动到目标节点之前
		if ("prev".equals(moveType)) {

			List<M> siblings = findSelfAndNextSiblings(target.getParentIds(), target.getWeight());
			//兄弟节点中包含源节点
			if (siblings.contains(source)) {
				// 1 2 [3 source] 4
				siblings = siblings.subList(0, siblings.indexOf(source) + 1);
				int firstWeight = siblings.get(0).getWeight();
				for (int i = 0; i < siblings.size() - 1; i++) {
					siblings.get(i).setWeight(siblings.get(i + 1).getWeight());
				}
				siblings.get(siblings.size() - 1).setWeight(firstWeight);
			} else {
				// 1 2 3 4  [5 new]
				int nextWeight = nextWeight(target.getParentId());
				int firstWeight = siblings.get(0).getWeight();
				for (int i = 0; i < siblings.size() - 1; i++) {
					siblings.get(i).setWeight(siblings.get(i + 1).getWeight());
				}
				siblings.get(siblings.size() - 1).setWeight(nextWeight);
				source.setWeight(firstWeight);
				updateSelftAndChild(source, target.getParentId(), target.getParentIds(), source.getWeight());
			}

			return;
		}
		//否则作为最后孩子节点
		int nextWeight = nextWeight(target.getId());
		updateSelftAndChild(source, target.getId(), target.makeSelfAsNewParentIds(), nextWeight);
	}


	/**
	 * 把源节点全部变更为目标节点
	 *
	 * @param source
	 * @param newParentIds
	 */
	public void updateSelftAndChild(M source, ID newParentId, String newParentIds, int newWeight) {
		String oldSourceChildrenParentIds = source.makeSelfAsNewParentIds();
		source.setParentId(newParentId);
		source.setParentIds(newParentIds);
		source.setWeight(newWeight);
		save(source);
		String newSourceChildrenParentIds = source.makeSelfAsNewParentIds();

		UPDATE_CHILDREN_PARENT_IDS_QL =
				String.format("update %s set parentIds=(?1 || substring(parentIds, length(?2)+1)) where parentIds like concat(?2, %s)", entityName, "'%'");

		repositoryHelper.batchUpdate(UPDATE_CHILDREN_PARENT_IDS_QL, newSourceChildrenParentIds, oldSourceChildrenParentIds);
	}

	/**
	 * 查找目标节点及之后的兄弟  注意：值与越小 越排在前边
	 *
	 * @param parentIds
	 * @param currentWeight
	 * @return
	 */
	public List<M> findSelfAndNextSiblings(String parentIds, int currentWeight) {

		FIND_SELF_AND_NEXT_SIBLINGS_QL =
				String.format("from %s where parentIds = ?1 and weight>=?2 order by weight asc", entityName);
		return repositoryHelper.<M>findAll(FIND_SELF_AND_NEXT_SIBLINGS_QL, parentIds, currentWeight);
	}


	/**
	 * 查看与name模糊匹配的名称
	 *
	 * @param name
	 * @return
	 */
	public Set<String> findNames(Searchable searchable, String name, ID excludeId) {
		M excludeM = findOne(excludeId);

		searchable.addSearchFilter("name", SearchOperator.like, name);
		addExcludeSearchFilter(searchable, excludeM);

		return Sets.newHashSet(
				Lists.transform(
						findAll(searchable).getContent(),
						new Function<M, String>() {
							@Override
							public String apply(M input) {
								return input.getName();
							}
						}
				)
		);

	}


	/**
	 * 查询子子孙孙
	 *
	 * @return
	 */
	public List<M> findChildren(List<M> parents, Searchable searchable) {

		if (parents.isEmpty()) {
			return Collections.EMPTY_LIST;
		}

		SearchFilter first = SearchFilterHelper.newCondition("parentIds", SearchOperator.prefixLike, parents.get(0).makeSelfAsNewParentIds());
		SearchFilter[] others = new SearchFilter[parents.size() - 1];
		for (int i = 1; i < parents.size(); i++) {
			others[i - 1] = SearchFilterHelper.newCondition("parentIds", SearchOperator.prefixLike, parents.get(i).makeSelfAsNewParentIds());
		}
		searchable.or(first, others);

		List<M> children = findAllWithSort(searchable);
		return children;
	}

	public List<M> findAllByName(Searchable searchable, M excludeM) {
		addExcludeSearchFilter(searchable, excludeM);
		return findAllWithSort(searchable);
	}

	/**
	 * 查找根和一级节点
	 *
	 * @param searchable
	 * @return
	 */
	public List<M> findRootAndChild(Searchable searchable) {
		searchable.addSearchParam("parentId_eq", 0);
		List<M> models = findAllWithSort(searchable);

		if (models.size() == 0) {
			return models;
		}
		List<ID> ids = Lists.newArrayList();
		for (int i = 0; i < models.size(); i++) {
			ids.add(models.get(i).getId());
		}
		searchable.removeSearchFilter("parentId_eq");
		searchable.addSearchParam("parentId_in", ids);

		models.addAll(findAllWithSort(searchable));

		return models;
	}

	public Set<ID> findAncestorIds(Iterable<ID> currentIds) {
		Set<ID> parents = Sets.newHashSet();
		for (ID currentId : currentIds) {
			parents.addAll(findAncestorIds(currentId));
		}
		return parents;
	}

	public Set<ID> findAncestorIds(ID currentId) {
		Set ids = Sets.newHashSet();
		M m = findOne(currentId);
		if (m == null) {
			return ids;
		}
		for (String idStr : StringUtils.tokenizeToStringArray(m.getParentIds(), "/")) {
			if (!StringUtils.isEmpty(idStr)) {
				ids.add(Long.valueOf(idStr));
			}
		}
		return ids;
	}

	/**
	 * 递归查询祖先
	 *
	 * @param parentIds
	 * @return
	 */
	public List<M> findAncestor(String parentIds) {
		if (StringUtils.isEmpty(parentIds)) {
			return Collections.EMPTY_LIST;
		}
		String[] ids = StringUtils.tokenizeToStringArray(parentIds, "/");

		return Lists.reverse(findAllWithNoPageNoSort(Searchable.newSearchable().addSearchFilter("id", SearchOperator.in, ids)));
	}


	public void addExcludeSearchFilter(Searchable searchable, M excludeM) {
		if (excludeM == null) {
			return;
		}
		searchable.addSearchFilter("id", SearchOperator.ne, excludeM.getId());
		searchable.addSearchFilter("parentIds", SearchOperator.suffixNotLike, excludeM.makeSelfAsNewParentIds());
	}

	public List<M> findAllWithNoPageNoSort(Searchable searchable) {
		searchable.removePageable();
		searchable.removeSort();
		return Lists.newArrayList(findAll(searchable).getContent());
	}

	public List<M> findAllWithSort(Searchable searchable) {
		searchable.removePageable();
		return Lists.newArrayList(findAll(searchable).getContent());
	}

}
